package com.cnwidsom.plugins.type;

import java.util.Map;

import com.cnwidsom.config.MonitorConfig;
import com.cnwidsom.filter.FilterChainFactory;
import com.cnwidsom.filter.FilterConfig;
import com.cnwidsom.filter.FilterUtils;
import com.cnwidsom.utils.ClassUtils;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;

public class JavaSqlTypeInjector implements TypeInjector {
	private Map<String, String> params;

	private final String[] INJECT_INTERFACES = new String[] { "java.sql.Statement", "java.sql.PreparedStatement", "java.sql.Connection" };

	public String getName() {
		return "javaSql";
	}

	private String buildCode(String recordMethodName, String callMethodName) {
		return buildCode(recordMethodName, callMethodName, null);
	}

	private String buildCode(String recordMethodName, String callMethodName, String type) {
		StringBuffer codeAppender = new StringBuffer();

		codeAppender.append("{\n");
		if (type != null && type.equals("prepare")) {
			codeAppender.append("String name = this.$getSql();\n");
			codeAppender.append("if(name==null){name=\"NOT FOUND SQL\";}\n");
			codeAppender.append("String fullsql = name;\n");
		} else if (type != null && type.equals("connection")) {

		} else {
			codeAppender.append("String name = $1;\n");
			codeAppender.append("String fullsql = name;\n");
		}

		codeAppender.append("java.util.List ex_list = new java.util.ArrayList();\n");
		if (type == null || !type.equals("connection")) {
			codeAppender.append("if (name.length() > 20){\n");
			codeAppender.append("name = name.substring(0, 20)+\"...\";}\n");
			codeAppender.append(
					"com.cnwidsom.monitor.Token token=com.cnwidsom.monitor.Token.createNextToken(name, com.cnwidsom.monitor.Token.TYPE_DB_CALLED, true);\n");
			codeAppender.append("long start = System.currentTimeMillis();\n");
		}

		codeAppender.append("try{\n");
		if (type != null && type.equals("connection")) {
			codeAppender.append("Object r = " + callMethodName + "($$);\n");
			codeAppender.append("com.cnwidsom.plugins.type.interfaces.SqlRecorder sqlRecorder = (com.cnwidsom.plugins.type.interfaces.SqlRecorder)r;\n");
			codeAppender.append("sqlRecorder.$setSql($1);\n");
			// codeAppender.append("System.out.println(\"connection:\"+sqlRecorder);\n");
			codeAppender.append("return (java.sql.PreparedStatement)r;\n");
		} else {
			codeAppender.append("return " + callMethodName + "($$);\n");
		}
		// $setSql

		codeAppender.append("}catch(java.sql.SQLException ex1){\n");
		codeAppender.append("ex_list.add(ex1);\n");
		codeAppender.append("throw ex1;\n");
		codeAppender.append("}finally{\n");

		if (type == null || !type.equals("connection")) {
			codeAppender.append("long runTime = System.currentTimeMillis()-start;\n");

			codeAppender.append("com.cnwidsom.monitor.MonitorItem mi = new com.cnwidsom.monitor.MonitorItem(com.cnwidsom.monitor.Token.getTokenHeader(), \""
					+ recordMethodName + "\", runTime, ex_list, System.currentTimeMillis(), fullsql);\n");
			codeAppender.append("java.util.Map params = new java.util.HashMap();\n");

			// 记录调用参数
			codeAppender.append("params.put(\"sql\",fullsql);\n");
			codeAppender.append("mi.setParameters(params);\n");
			codeAppender.append("com.cnwidsom.monitor.Token.endBackToken(mi);\n");
			codeAppender.append("java.util.List token_list = com.cnwidsom.monitor.Token.releaseToken();\n");
			codeAppender.append("if(token_list!=null&&token_list.size()>0){\n");
			codeAppender.append("com.cnwidsom.monitor.MonitorLogWritter.log(token_list);\n");
			codeAppender.append("}\n");
		}
		codeAppender.append("}\n");
		codeAppender.append("}");

		// System.out.println(codeAppender);
		return codeAppender.toString();
	}

	public byte[] inject(ClassLoader loader, CtClass ctClass) {
		try {
			if (ctClass.isInterface()) {
				return null;
			}

			if (ClassUtils.getRelationBetweenClasses(ctClass, "java.sql.Statement") == ClassUtils.CLASS_RELATION_CHILD) {

				CtMethod method_query = ClassUtils.getMatchMethod(ctClass, "executeQuery", new String[] { "java.lang.String" });

				CtMethod method_update_1 = ClassUtils.getMatchMethod(ctClass, "executeUpdate", new String[] { "java.lang.String", "int" });
				CtMethod method_update_2 = ClassUtils.getMatchMethod(ctClass, "executeUpdate", new String[] { "java.lang.String", "int[]" });
				CtMethod method_update_3 = ClassUtils.getMatchMethod(ctClass, "executeUpdate", new String[] { "java.lang.String", "java.lang.String[]" });
				if (method_update_1 != null && method_update_2 != null && method_update_3 != null) {

					CtMethod method_exe_1 = ClassUtils.getMatchMethod(ctClass, "execute", new String[] { "java.lang.String", "int" });
					CtMethod method_exe_2 = ClassUtils.getMatchMethod(ctClass, "execute", new String[] { "java.lang.String", "int[]" });
					CtMethod method_exe_3 = ClassUtils.getMatchMethod(ctClass, "execute", new String[] { "java.lang.String", "java.lang.String[]" });
					CtMethod method_exe_4 = ClassUtils.getMatchMethod(ctClass, "execute", new String[] { "java.lang.String" });

					if (method_exe_1 != null && method_exe_2 != null && method_exe_3 != null && method_exe_4 != null) {

						System.out.println("Injecting " + getName() + "[" + ctClass.getName() + "]...");

						String old_query_name = method_query.getName();
						String new_query_name = old_query_name + "$impl";

						String old_update_1_name = method_update_1.getName();
						String new_update_1_name = old_update_1_name + "$impl";
						String old_update_2_name = method_update_2.getName();
						String new_update_2_name = old_update_2_name + "$impl";
						String old_update_3_name = method_update_3.getName();
						String new_update_3_name = old_update_3_name + "$impl";

						String old_exe_1_name = method_exe_1.getName();
						String new_exe_1_name = old_exe_1_name + "$impl";
						String old_exe_2_name = method_exe_2.getName();
						String new_exe_2_name = old_exe_2_name + "$impl";
						String old_exe_3_name = method_exe_3.getName();
						String new_exe_3_name = old_exe_3_name + "$impl";
						String old_exe_4_name = method_exe_4.getName();
						String new_exe_4_name = old_exe_4_name + "$impl";

						CtMethod method_new_query = CtNewMethod.copy(method_query, new_query_name, ctClass, null);

						CtMethod method_new_update_1 = CtNewMethod.copy(method_update_1, new_update_1_name, ctClass, null);
						CtMethod method_new_update_2 = CtNewMethod.copy(method_update_2, new_update_2_name, ctClass, null);
						CtMethod method_new_update_3 = CtNewMethod.copy(method_update_3, new_update_3_name, ctClass, null);

						CtMethod method_new_exe_1 = CtNewMethod.copy(method_exe_1, new_exe_1_name, ctClass, null);
						CtMethod method_new_exe_2 = CtNewMethod.copy(method_exe_2, new_exe_2_name, ctClass, null);
						CtMethod method_new_exe_3 = CtNewMethod.copy(method_exe_3, new_exe_3_name, ctClass, null);
						CtMethod method_new_exe_4 = CtNewMethod.copy(method_exe_4, new_exe_4_name, ctClass, null);

						// 增加新方法
						ctClass.addMethod(method_new_query);
						ctClass.addMethod(method_new_update_1);
						ctClass.addMethod(method_new_update_2);
						ctClass.addMethod(method_new_update_3);
						ctClass.addMethod(method_new_exe_1);
						ctClass.addMethod(method_new_exe_2);
						ctClass.addMethod(method_new_exe_3);
						ctClass.addMethod(method_new_exe_4);

						method_query.setBody(buildCode(method_query.getLongName(), new_query_name));

						method_update_1.setBody(buildCode(method_update_1.getLongName(), new_update_1_name));
						method_update_2.setBody(buildCode(method_update_2.getLongName(), new_update_2_name));
						method_update_3.setBody(buildCode(method_update_3.getLongName(), new_update_3_name));

						method_exe_1.setBody(buildCode(method_exe_1.getLongName(), new_exe_1_name));
						method_exe_2.setBody(buildCode(method_exe_2.getLongName(), new_exe_2_name));
						method_exe_3.setBody(buildCode(method_exe_3.getLongName(), new_exe_3_name));
						method_exe_4.setBody(buildCode(method_exe_4.getLongName(), new_exe_4_name));
					}
				}

			}

			if (ClassUtils.getRelationBetweenClasses(ctClass, "java.sql.PreparedStatement") == ClassUtils.CLASS_RELATION_CHILD) {
				CtMethod[] method_exes = new CtMethod[3];
				method_exes[0] = ClassUtils.getMatchMethod(ctClass, "execute", new String[] {});
				method_exes[1] = ClassUtils.getMatchMethod(ctClass, "executeQuery", new String[] {});
				method_exes[2] = ClassUtils.getMatchMethod(ctClass, "executeUpdate", new String[] {});

				CtField field = CtField.make("private String $_sql;", ctClass);
				ctClass.addField(field);

				CtMethod setSqlFunc = CtNewMethod.make("public void $setSql(String sql) {this.$_sql = sql;}", ctClass);
				CtMethod getSqlFunc = CtNewMethod.make("public String $getSql() {return $_sql;}", ctClass);
				ctClass.addMethod(getSqlFunc);
				ctClass.addMethod(setSqlFunc);

				ctClass.addInterface(ctClass.getClassPool().get("com.cnwidsom.plugins.type.interfaces.SqlRecorder"));

				for (CtMethod method_exe : method_exes) {
					if (method_exe != null) {

						String old_exe_name = method_exe.getName();
						String new_exe_name = old_exe_name + "$impl";
						CtMethod method_new_exe = CtNewMethod.copy(method_exe, new_exe_name, ctClass, null);
						ctClass.addMethod(method_new_exe);
						method_exe.setBody(buildCode(method_exe.getLongName(), new_exe_name, "prepare"));
					}
				}
			}

			if (ClassUtils.getRelationBetweenClasses(ctClass, "java.sql.Connection") == ClassUtils.CLASS_RELATION_CHILD) {
				CtMethod[] method_preps = new CtMethod[4];
				method_preps[0] = ClassUtils.getMatchMethod(ctClass, "prepareStatement", new String[] { "java.lang.String" });
				method_preps[1] = ClassUtils.getMatchMethod(ctClass, "prepareStatement", new String[] { "java.lang.String", "int" });
				method_preps[2] = ClassUtils.getMatchMethod(ctClass, "prepareStatement", new String[] { "java.lang.String", "int[]" });
				method_preps[3] = ClassUtils.getMatchMethod(ctClass, "prepareStatement", new String[] { "java.lang.String", "java.lang.String[]" });
				for (CtMethod method_prep : method_preps) {
					if (method_prep != null) {
						String old_prep_name = method_prep.getName();
						String new_prep_name = old_prep_name + "$impl";

						CtMethod method_new_prep = CtNewMethod.copy(method_prep, new_prep_name, ctClass, null);

						ctClass.addMethod(method_new_prep);

						method_prep.setBody(buildCode(method_prep.getLongName(), new_prep_name, "connection"));

					}
				}
			}
			return ctClass.toBytecode();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	public boolean canInject(String nClassName) {
		if (params.get("dbType") != null) {
			if (params.get("dbType").equalsIgnoreCase("mysql")) {
				if (!nClassName.startsWith("com.mysql.jdbc.")) {
					return false;
				}
			}
		}
		for (String ifname : INJECT_INTERFACES) {
			if (ClassUtils.getRelationBetweenClasses(nClassName, ifname) == ClassUtils.CLASS_RELATION_CHILD) {
				return true;
			}
		}
		return false;
	}

	public void setParameters(Map<String, String> params) {
		this.params = params;
	}

	public void init() {
		if (MonitorConfig.getCur_mc().getFilterChains().get(FilterUtils.FILTER_TYPE_LOG) == null) {
			MonitorConfig.getCur_mc().getFilterChains().put(FilterUtils.FILTER_TYPE_LOG, FilterChainFactory.getFilterChain());
		}
		FilterConfig config = new FilterConfig("com.cnwidsom.filter.SimpleMysqlDBLogFilter", FilterUtils.FILTER_TYPE_LOG);
		MonitorConfig.getCur_mc().getFilterChains().get(FilterUtils.FILTER_TYPE_LOG).addFilter(config);
	}

}
